package com.semlab.client.fwk.ap;

import static com.semlab.client.fwk.ap.PlaceConstants.ID_SEPARATOR;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


public class Place extends com.google.gwt.place.shared.Place {

	public static class Parameter<T> {

		String name;
		Coder<T> coder;

		public Parameter(java.lang.String name, Coder<T> coder) {
			this.name = name;
			this.coder = coder;
		}
	}

	public static interface Coder<T> {
		T decode(String value);

		String encode(T value);
	}

	public static final Coder<String> String = new Coder<String>() {
		@Override
		public String decode(String value) {
			return value;
		}

		@Override
		public String encode(String value) {
			return value;
		}
	};

	public static final Coder<Integer> Integer = new Coder<Integer>() {
		@Override
		public Integer decode(String value) {
			try {
				return java.lang.Integer.valueOf(value);
			} catch (Exception e) {
				return null;
			}
		}

		@Override
		public String encode(Integer value) {
			return value.toString();
		}
	};

	public static final Coder<Long> Long = new Coder<Long>() {
		@Override
		public Long decode(String value) {
			try {
				return java.lang.Long.valueOf(value);
			} catch (Exception e) {
				return null;
			}
		}

		@Override
		public String encode(Long value) {
			return value.toString();
		}
	};

	public static final Coder<Boolean> Boolean = new Coder<Boolean>() {
		@Override
		public Boolean decode(String value) {
			return java.lang.Boolean.parseBoolean(value);
		}

		@Override
		public String encode(Boolean value) {
			return value.toString();
		}
	};
	
	public static final Coder<Place> Place = new Coder<Place>() {
		@Override
		public Place decode(String value) {
			String[] pars = value.split(ID_SEPARATOR);
			if(pars.length != 2){
				return null;
			}
			Place place = new Place(pars[0], pars[1]);
			return place;
		}

		@Override
		public String encode(Place value) {
			return value.getContainerId()+ID_SEPARATOR+value.getActivityId();
		}
	};

	private final String containerId;
	private final String activityId;
	private Map<String, String> params = new HashMap<String, String>();

	public Place(String containerId, String activityId) {
		this.containerId = containerId;
		this.activityId = activityId;
	}

	private Place(Place req, String name, String value) {
		this.containerId = req.containerId;
		this.activityId = req.activityId;
		if (req.params != null)
			this.params.putAll(req.params);
		if (value != null)
			this.params.put(name, value);
	}

	public String getContainerId(){
		return containerId;
	}
	

	public String getActivityId() {
		return activityId;
	}

	public Set<String> getParameterNames() {
		if (params != null) {
			return params.keySet();
		} else {
			return Collections.emptySet();
		}
	}
	
	public boolean hasParametar(Parameter parameter){
		if (params != null) {
			return params.containsKey(parameter.name);
		}
		return false;
	}
	
	public ArrayList<Place> getParameterPlaces(){
		ArrayList<Place> places = new ArrayList<Place>();
		if (params != null) {
			Iterator<String> iterator = params.values().iterator();
			while (iterator.hasNext()) {
				String string = (String) iterator.next();
				Place place = Place.decode(string);
				if(place != null){
					places.add(place);
				}
			}
		}
		return places;
	}

	public String get(String key) {
		return get(key, String);
	}

	public <T> T get(String key, Coder<T> coder) {
		return coder.decode(params.get(key));
	}

	public <T> T get(Parameter<T> parameter) {
		return parameter.coder.decode(params.get(parameter.name));
	}

	/**
	 * Returns a new instance of the request with the specified parameter name
	 * and value. If a parameter with the same name was previously specified,
	 * the new request contains the new value.
	 * 
	 * @param name
	 *            The new parameter name.
	 * @param value
	 *            The new parameter value.
	 * @return The new place request instance.
	 */
	public Place with(String name, String value) {
		return with(name, value, String);
	}

	public Place with(String name, Integer value) {
		return with(name, value, Integer);
	}

	<T> Place with(String name, T value, Coder<T> coder) {
		return new Place(this, name, coder.encode(value));
	}

	public <T> Place with(Parameter<T> param, T value) {
		return new Place(this, param.name, param.coder.encode(value));
	}

	public <T> Place and(Parameter<T> param, T value) {
		return with(param, value);
	}

	@Override
	public String toString() {
		return "Place [containerId=" + containerId + ", name=" + activityId + ", params=" + params + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((containerId == null) ? 0 : containerId.hashCode());
		result = prime * result + ((activityId == null) ? 0 : activityId.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Place other = (Place) obj;
		if (containerId == null) {
			if (other.containerId != null)
				return false;
		} else if (!containerId.equals(other.containerId))
			return false;
		if (activityId == null) {
			if (other.activityId != null)
				return false;
		} else if (!activityId.equals(other.activityId))
			return false;
		return true;
	}

}
